﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Extensil.Common;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using Extensil.Category;
using Extensil.Common.CategoryHandler;
using Extensil.Processor;
using Extensil.Processor.Extensibility;
using System.Data;


namespace Extensil.Category
{
    public class CategoryFactory
    {
        static List<ComboPropertyParam> _MadeVisibleCategories = new List<ComboPropertyParam>();
        static List<ComboPropertyParam> _AllCategories;
        static List<ComboPropertyParam> _CategoriesWithSetting;
        static List<ComboPropertyParam> _CategoriesWithHelp;
        public static void SetCategoryVisible(string categoryName)
        {
            string catName = CategoryFactory.GetSystemCategoryName(categoryName);
            foreach(ComboPropertyParam param in _MadeVisibleCategories)
            {
                if(param.Key == catName)
                    return;
            }
            _MadeVisibleCategories.Add(new ComboPropertyParam(catName, catName));
        }
        public static BaseCategory GetCategoryForm(string category)
        {
            return GetCategory(category);
        }
        static BaseCategory GetCategory(string categoryName)
        {
            BaseCategory bCat = null;
            Type[] bTypes = Assembly.GetExecutingAssembly().GetTypes();
            foreach(Type t in bTypes)
            {
                foreach(object attr in t.GetCustomAttributes(typeof(CategoryAttribute), true))
                {
                    CategoryAttribute atr = attr as CategoryAttribute;
                    if(atr.Name == categoryName)
                    {
                        return (BaseCategory)Activator.CreateInstance(t);
                    }
                }

                if(bCat == null && t.BaseType == (typeof(BaseCategory))
                    && t.GetConstructor(Type.EmptyTypes) != null
                    && t.Name.ToLower() == categoryName.ToLower())
                {
                    bCat = (BaseCategory)Activator.CreateInstance(t);
                }
            }

            foreach(Extensil.Common.CategoryHandler.PluginHandler cat in CategorySection.GetConfig().CategoryHandlers)
            {
                if(cat.Name == categoryName)
                    return LoadCustomCategory(categoryName);
            }

            return bCat;
        }
        internal static List<ComboPropertyParam> GetAllCategories()
        {
            if(_AllCategories != null)
                return _AllCategories;
            _AllCategories = new List<ComboPropertyParam>();

            PluginHandlers catList = CategorySection.GetConfig().CategoryHandlers;
            foreach(Extensil.Common.CategoryHandler.PluginHandler cat in catList)
            {
                _AllCategories.Add(new ComboPropertyParam(cat.Name, cat.Name, cat.Order, !cat.Hidden, cat.AutoStart, cat.HotKey));
            }
            _AllCategories.Sort(
                delegate(ComboPropertyParam p1, ComboPropertyParam p2)
                {
                    return p1.Order.CompareTo(p2.Order);
                }
            );
            List<string> tCategoryList = CategoryFactory.GetSystemCategories();
            foreach(string tCategory in tCategoryList)
            {
                string catName = CategoryFactory.GetSystemCategoryName(tCategory);
                CategoryAttribute cAttr = GetCategoryAttributes(tCategory);
                _AllCategories.Add(new ComboPropertyParam(catName, catName, -1, cAttr.Visible, cAttr.AutoStart, cAttr.HotKey));
            }

            return _AllCategories;
        }
        internal static List<ComboPropertyParam> GetAllVisibleCategories()
        {
            List<ComboPropertyParam> allCats = GetAllCategories();
            List<ComboPropertyParam> retCats = new List<ComboPropertyParam>();
            foreach(ComboPropertyParam par in allCats)
            {
                if(par.Visible)
                    retCats.Add(par.Copy);
            }
            retCats.AddRange(_MadeVisibleCategories);
            return retCats;
        }
        internal static List<ComboPropertyParam> GetAllAutoStartCategories()
        {
            List<ComboPropertyParam> allCats = GetAllCategories();
            List<ComboPropertyParam> retCats = new List<ComboPropertyParam>();
            foreach(ComboPropertyParam par in retCats)
            {
                if(par.AutoStart)
                    retCats.Add(par.Copy);
            }
            return retCats;
        }
        internal static List<ComboPropertyParam> GetCategoriesWithSetting()
        {
            if(_CategoriesWithSetting != null)
                return _CategoriesWithSetting;
            _CategoriesWithSetting = new List<ComboPropertyParam>();
            List<BaseCategory> bCatList = LoadAllCustomCategory();
            bCatList.AddRange(LoadAllSystemCategory());
            foreach(BaseCategory bCat in bCatList)
            {
                if(bCat.Setting != null)
                {
                    _CategoriesWithSetting.Add(new ComboPropertyParam(bCat.Name, bCat.Name));
                }
            }

            return _CategoriesWithSetting;
        }
        internal static List<ComboPropertyParam> GetCategoriesWithHelp()
        {
            if(_CategoriesWithHelp != null)
                return _CategoriesWithHelp;
            _CategoriesWithHelp = new List<ComboPropertyParam>();
            List<BaseCategory> bCatList = LoadAllCustomCategory();
            bCatList.AddRange(LoadAllSystemCategory());
            foreach(BaseCategory bCat in bCatList)
            {
                if(bCat.Help != null)
                {
                    _CategoriesWithHelp.Add(new ComboPropertyParam(bCat.Name, bCat.Name));
                }
            }

            return _CategoriesWithHelp;
        }

        internal static void Reset()
        {
            _AllCategories = null;
            _CategoriesWithSetting = null;
        }

        #region System Category
        internal static bool IsSystemCategoryVisible(string categoryClassName)
        {
            Type[] bTypes = Assembly.GetExecutingAssembly().GetTypes();
            foreach(Type t in bTypes)
            {
                if(t.BaseType == (typeof(BaseCategory))
                    && t.GetConstructor(Type.EmptyTypes) != null
                    && t.Name.ToLower() == categoryClassName.ToLower())
                {
                    foreach(object attr in t.GetCustomAttributes(typeof(CategoryAttribute), true))
                    {
                        CategoryAttribute atr = attr as CategoryAttribute;
                        return atr.Visible;
                    }
                    break;
                }
            }
            return true;
        }
        internal static bool IsSystemCategoryAutoStart(string categoryClassName)
        {
            Type[] bTypes = Assembly.GetExecutingAssembly().GetTypes();
            foreach(Type t in bTypes)
            {
                if(t.BaseType == (typeof(BaseCategory))
                    && t.GetConstructor(Type.EmptyTypes) != null
                    && t.Name.ToLower() == categoryClassName.ToLower())
                {
                    foreach(object attr in t.GetCustomAttributes(typeof(CategoryAttribute), true))
                    {
                        CategoryAttribute atr = attr as CategoryAttribute;
                        return atr.AutoStart;
                    }
                    break;
                }
            }
            return false;
        }
        internal static string GetSystemCategoryName(string categoryClassName)
        {
            Type[] bTypes = Assembly.GetExecutingAssembly().GetTypes();
            foreach(Type t in bTypes)
            {
                if(t.BaseType == (typeof(BaseCategory))
                    && t.GetConstructor(Type.EmptyTypes) != null
                    && t.Name.ToLower() == categoryClassName.ToLower())
                {
                    foreach(object attr in t.GetCustomAttributes(typeof(CategoryAttribute), true))
                    {
                        CategoryAttribute atr = attr as CategoryAttribute;
                        return atr.Name;
                    }
                    break;
                }
            }
            return categoryClassName;
        }
        internal static CategoryAttribute GetCategoryAttributes(string categoryClassName)
        {
            Type[] bTypes = Assembly.GetExecutingAssembly().GetTypes();
            foreach(Type t in bTypes)
            {
                if(t.BaseType == (typeof(BaseCategory))
                    && t.GetConstructor(Type.EmptyTypes) != null
                    && t.Name.ToLower() == categoryClassName.ToLower())
                {
                    foreach(object attr in t.GetCustomAttributes(typeof(CategoryAttribute), true))
                    {
                        return attr as CategoryAttribute;
                    }
                    break;
                }
            }
            return null;
        }
        static List<BaseCategory> LoadAllSystemCategory()
        {
            return (from t in Assembly.GetExecutingAssembly().GetTypes()
                    where t.BaseType == (typeof(BaseCategory)) && t.GetConstructor(Type.EmptyTypes) != null 
                    orderby t.Name ascending
                    select (BaseCategory)Activator.CreateInstance(t)).ToList();
        }
        internal static List<string> GetSystemCategories()
        {
            return (from t in Assembly.GetExecutingAssembly().GetTypes()
                    where t.BaseType == (typeof(BaseCategory)) && t.GetConstructor(Type.EmptyTypes) != null
                    orderby t.Name ascending
                    select t.Name).ToList();
        }
        
        #endregion

        #region Custom Category
        internal static BaseCategory LoadCustomCategory(string categoryName)
        {
            return LoadCustomCategory(categoryName, true);
        }
        static BaseCategory LoadCustomCategory(string categoryName, bool throwExceptionOnError)
        {
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            Extensil.Common.CategoryHandler.PluginHandler cat = CategorySection.GetConfig().CategoryHandlers[categoryName];
            if(cat == null)
                if(throwExceptionOnError)
                    throw new Exception("Custom category " + categoryName + " is not registered.");
                else
                    return null;

            string path = cat.Path;
            string[] catClass = path.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            string hpath = PluginAssembly.GetPluginHandler(catClass[1].Trim());
            if(String.IsNullOrEmpty(hpath))
                if(throwExceptionOnError)
                    throw new Exception("Custom category " + catClass[1].Trim() + ".dll could not be found.");
                else
                    return null;

            Assembly asm = PluginAssembly.Load(hpath);
            try
            {
                BaseCategory retCat = (BaseCategory)asm.CreateInstance(catClass[0].Trim());
                retCat.SetName(categoryName);
                return retCat;
            }
            catch(Exception exc)
            {
                throw exc;
            }
        }
        static List<BaseCategory> LoadAllCustomCategory()
        {
            List<BaseCategory> retValue = new List<BaseCategory>();
            foreach(Extensil.Common.CategoryHandler.PluginHandler cat in CategorySection.GetConfig().CategoryHandlers)
            {
                string path = cat.Path;
                string[] catClass = path.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                string hpath = PluginAssembly.GetPluginHandler(catClass[1].Trim());
                if(!String.IsNullOrEmpty(hpath))
                {
                    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
                    Assembly asm = PluginAssembly.Load(hpath);
                    BaseCategory bc = (BaseCategory)asm.CreateInstance(catClass[0].Trim());
                    bc.SetName(cat.Name);
                    bc.Order = cat.Order;
                    retValue.Add(bc);
                }
            }
            retValue.Sort(
                delegate(BaseCategory p1, BaseCategory p2)
                {
                    return p1.Order.CompareTo(p2.Order);
                }
            );
            return retValue;
        }
        public static List<ComboPropertyParam> GetCustomCategories()
        {
            List<ComboPropertyParam> retValue = new List<ComboPropertyParam>();
            PluginHandlers catList = CategorySection.GetConfig().CategoryHandlers;
            foreach(Extensil.Common.CategoryHandler.PluginHandler cat in catList)
            {
                retValue.Add(new ComboPropertyParam(cat.Name, cat.Name, cat.Order));
            }
            retValue.Sort(
                delegate(ComboPropertyParam p1, ComboPropertyParam p2)
                {
                    return p1.Order.CompareTo(p2.Order);
                }
            );
            return retValue;
        }
        public static DataTable SortedCategoriesDataSet
        {
            get
            {
                return ConfigModifier.SortedCategoriesDataSet;
            }
        }
        static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            string s = Application.StartupPath + "/" + 
                CategorySection.GetConfig().RootFolder + "/" + 
                (args.Name.Contains(',') ? args.Name.Remove(args.Name.IndexOf(',')) : args.Name) + ".dll";
            return PluginAssembly.Load(s);
        }
        #endregion
    }
}
